At the beginning of this year, I decided it was finally time to port my website from a PHP-based CMS[1] to a JavaScript-based Static Site Generator (SSG). This was for a few reasons:
- After starting out as a ‘full-stack’ developer, I now work solely on the front end: If I need to write custom functionality, I don’t want to write it in PHP when I could be writing it in JavaScript.
- I don't need the layers of abstraction or the complexity of a CMS — markdown files are where I'm most comfortable writing content and I’d be happy never to touch a MySQL database or wysiwyg editor again.
- I want to improve my site’s performance: static HTML files are, 99 times out of 100, going to be faster than pages built on the fly.
- Lastly, there’s the cost benefit: A LAMP stack[2] server costs money every month; Netlify’s free tier (300 build minutes/month) should comfortably cover a personal blog at zero cost.
Once I'd decided I want a Static Site Generator and it needed to use JavaScript (which ruled out Jekyll and Hugo), I narrowed the list down to two very different contenders:
“I heard Gatsby was good”
According to the official site, “Gatsby is a free and open source framework based on React that helps developers build blazing fast websites and apps”. It has a data layer powered by GraphQL and it outputs everything to static files, allowing you to host it pretty much anywhere.
When I first heard that I could write React and use this cool new GraphQL thing, while still outputting static pages that work without JavaScript, I was keen to give it a try. ‘This sounds like Progressive Enhancement but without the effort’ I thought. Unfortunately, as with most things that sound too good to be true, after some investigation, there turned out to be a catch. Here's what happens the first time a user visits a Gatsby site:
- The user requests a page
- The server sends the statically-generated HTML document to the user’s browser and the browser starts rendering the page.
- Now that the HTML document has arrived, the JavaScript bundle (including the React library and any other JavaScript required to render the page) begins downloading, parsing and compiling in the background
- The JavaScript is ready to run — the whole DOM gets “hydrated”[3] with React components
There’s something not quite right here — Gatsby forces you to load the same page again, but as React components; until that extra step is complete, none of the elements that require JavaScript (e.g. buttons, menus, custom inputs) are actually interactive.
If your site doesn’t have any interactive elements (excluding links, they work without JavaScript, even in Gatsby), your users still have to download this JavaScript anyway, for the sole purpose of turning your site into a Single-Page Application (SPA), which has its own drawbacks as we'll soon discover.
This extra bloat seems to go against one of the main reasons for moving to a SSG: making pages faster. Your flashy Gatsby site might be fast on a $2,000 MacBook, but for someone using a budget smartphone on a 3G connection, it's visible yet unresponsive, for 15 seconds while the user waits for the JavaScript to load. It’s also unnecessarily draining their battery and data allowance.
If the browser needs to parse 296kB of JavaScript[4] to show a list of blog posts, that’s not Progressive Enhancement, it’s using the wrong tool for the job. To use the rather fuzzy website/web app distinction — React is for building web apps: interactive UIs that need to respond to user input or fetch data in real time — your blog is just a website and that’s fine.
Accessibility in single-page applications
A single-page application is a website that forgoes the traditional method of navigation on the web, i.e. loading new content by loading a new HTML document; instead it uses JavaScript features like AJAX and the History API to swap in new content without triggering a page load. The goal is to increase perceived performance and make the website feel more like a 'native' app (something you’d download from an app store). The problem with getting rid of full-page reloads is that browsers and assistive technology use page loads as a signal to trigger certain useful behaviours, including announcing the title of the new page or resetting keyboard focus to the beginning of the document.
If you’re an accessibility-conscious developer building a single-page application, you may try to emulate the browser’s behaviour using JavaScript. Gatsby attempts to handle this for you by including a RouteAnnouncer
component. This uses an ARIA live region to announce the title
or h1
of the page, alerting users of screen reader software to navigation changes. This approach, however, is not without its problems: there are still outstanding issues around configuration and localisation.
We’ve seen that the Single-page applications have inherent accessibility issues around navigation, but it’s worth bearing in mind that using a front-end framework can make other aspects of accessibility harder too. In a February 2020 survey of one million homepages, WebAIM found that those using React had 5.7% more accessibility errors than average; while those using Vue had 25% more[5]. This doesn’t necessarily mean that the frameworks caused these errors, but there’s a strong correlation between more JavaScript and worse accessibility.
Does a blog even need JavaScript?
Chances are, the first webpage your ever built performed far better than some of the pages you've built since — it consisted of an HTML file and some CSS; maybe it had a few unoptimised images but they didn’t stop the page from loading. If you're anything like me, the moment you started adding JavaScript was the moment the performance of your webpage took a nosedive. Not all bytes are made equal: an image is going to take far less time to decode and render to the screen than the equivalently sized JavaScript file would take to parse, compile, and execute[6].
JavaScript is a powerful language that can do some incredible things, but it’s incredibly easy to jump to using it too early in development, when you could be using HTML and CSS instead. Consider the rule of least power: Don’t use the more powerful language (JavaScript) until you’ve exhausted the capabilities of less powerful languages (HTML). To me, it feels that turning a blog into a JavaScript Single-page application is introducing unnecessary complexity.
I don't want this post to come across as a specific attack on Gatsby. There are some clever people behind it, who’ve acknowledged many of the issues mentioned in this article and are trying to solve them. A statically rendered and hydrated page is still infinitely better than a fully client-side rendered React app (like those generated by create-react-app) which is useless without JavaScript. I do have issues with the way that its JavaScript-heavy, Single page app approach is advertised, including in Gatsby’s own marketing, as appropriate for any kind of website. Client-side JavaScript has a cost and developers should be aware of it.
Building a Gatsby site with less JavaScript
This brings me to a dilemma: developing sites with Gatsby is an all-round great experience; but Developer Experience (DX) should always be secondary to User Experience (UX) — how do I build a Gatsby site without the issues inherent in Gatsby’s JavaScript-heavy approach? By removing as much of it as possible, of course. Fortunately, there are some fantastic efforts going on within the Gatsby community to build faster, more lightweight sites:
Firstly, you could save a few kilobytes by swapping React for Preact, with gatsby-plugin-preact. I use this on the Component Gallery and it works seamlessly, instantly knocking ~30kB off the JavaScript payload.
If you want to take a more drastic approach, there's a plugin which removes all the Gatsby JavaScript from your Gatsby site. Now we’re getting somewhere! You can continue to write react components and GraphQL, even use a CSS-in-JS library (as long as it outputs CSS or inline styles), without sending any JavaScript to the browser. Most of the problems with Gatsby can be solved by throwing out all of its client side JavaScript. Gatsby Starter Low Tech Blog uses the no-javascript plugin, as well as some other techniques including converting all images to greyscale, to help you create an incredibly lightweight and energy-efficient blog.
Starting from scratch with Eleventy
At this point I felt like something was wrong — using a framework that heavily pushes client-side JavaScript, but removing all of that JavaScript seemed like a pretty convoluted way to build a website. I wanted to see whether I could build a fully-featured blog with zero client-side JavaScript and instead of using a plugin to remove it, I wasn’t going to add it in the first place. This is where my other contender comes in:
Eleventy encourages you to build your site how you want to. You use the technology you're most comfortable with and it sticks to generating your pages. Eleventy gives you the option of mixing and matching ten different templating languages, including markdown, nunjucks, and liquid; this meant I could copy and paste my old twig templates from Craft, change the file extension, and with some minor tweaks have them running in Eleventy. Instead of adapting my front-end build process to a new bundler I could just drop in my existing webpack file and src
folder. Using the concurrently package I can run my build script at the same time as Eleventy’s serve
process.
Like Gatsby, Eleventy also has an ecosystem of plugins (which is small, but rapidly growing). I’ve picked out a few that I've used which have enabled me to add features without adding client-side JavaScript:
When displaying code snippets in a post, it’s common to include language-specific syntax highlighting. There are a few JavaScript libraries out there that can do it, the most popular of which seems to be Prism — normally you’d run this in the client, but because we’re using a JavaScript SSG, we can run it at build time and bake the HTML elements and CSS classes required for syntax highlighting directly into the document — this removes the need to download the library in the browser.
eleventy-plugin-embed-tweet is another example of this approach of running the JavaScript at build time instead of in the client. Twitter’s default embed code forces the user to download vast quantities of JavaScript just to render a tweet. By fetching and rendering the tweet at build time, the user gets the bare minimum of HTML and CSS required to render the tweet and no extra JavaScript at all.
As with any new technology, Eleventy is lacking some of the features of other more well-established tools. For example, there’s no elegant method for generating responsive images in Eleventy; compare this with Gatsby’s excellent gatsby-image plugin, which can generate lazy-loading, responsive picture elements and smoothly transition from a low resolution or SVG version of the image once the full-resolution file is loaded. There are also a few things I’ve found confusing: I struggled to understand the pagination feature for a while, thinking it was solely for paginating posts into groups of a specified size, before realising it could generate entirely new pages dynamically; I also find myself mixing template languages within the same file: there’s nothing to stop you including nunjucks tags in a markdown file, or swapping out yaml-based frontmatter for JavaScript, but this can break syntax-highlighting, linting and autoformatting.
Conclusion
If you do decide to go with Gatsby I don’t blame you — sometimes it’s nice to use an opinionated framework and if you want to get something up and running quickly, it’s a solid solution. Just be aware of the performance costs and potential impact on accessibility associated with all that JavaScript.
I've chosen to use Eleventy to build my website, but I understand starting with a blank canvas is not for everyone — having the freedom to build something exactly how you want can be daunting. But you don’t have to approach it the way I have — similar to Gatsby, Eleventy has lots of starter projects that you can use as a base. Some of these, such as Andy Bell’s Hylia starter kit can be up and running in a matter of minutes; it even comes pre-configured with Netlify CMS so you can edit your site’s content without touching any code.
So what have I learned? Eleventy makes it easy to build a blog without JavaScript, but there will always be some features that need client-side JavaScript:
- I’ve had to remove Google Analytics from my site, but analytics was never really good for users in the first place, so I’m not sad to see it go — fortunately, there are server-side alternatives which I will cover in another post.
- I’ve used the
loading="lazy"
attribute for lazy loading images, but browser support is patchy and until the native browser implementation improves, there’s no way to gradually fade in images as they load. - A dark mode toggle — while I could probably hack this together using only CSS, without access to cookies or local storage, I’d have no way of persisting the value between pages.
Am I going to add JavaScript to my site any time soon? Probably not: the features I’ve listed above are nothing more than nice-to-haves. I’m not seriously suggesting that everyone reading this opens up their website and deletes every single JavaScript file, but from now on, when building websites, I will try to think of JavaScript as an optional extra, not a fundamental part of the experience. I encourage you to do the same.
Linux, Apache, MySQL, PHP: a common hosting setup for cheap shared servers ↩︎
A process whereby React attempts to match elements in the existing HTML markup to its own components and state, effectively re-running the logic which rendered the static HTML file at build time, but in the client ↩︎
The amount of JavaScript loaded on the homepage of Gatsby Starter Blog (99kB gzipped), measured 2020-04-11 ↩︎
The WebAIM Million: An annual accessibility analysis of the top 1,000,000 home pages ↩︎